{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-10-20T08:38:39.173539Z",
     "start_time": "2025-10-20T08:38:39.164714Z"
    }
   },
   "source": [
    "import os\n",
    "from dotenv import load_dotenv,find_dotenv\n",
    "load_dotenv(find_dotenv())"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:38:39.191577Z",
     "start_time": "2025-10-20T08:38:39.190050Z"
    }
   },
   "cell_type": "code",
   "source": "API_KEY = os.getenv(\"SERPER_API_KEY\")",
   "id": "318cfaf3745714f8",
   "outputs": [],
   "execution_count": 2
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:38:42.435712Z",
     "start_time": "2025-10-20T08:38:42.413099Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import httpx\n",
    "\n",
    "def google_search(query):\n",
    "    url = \"https://google.serper.dev/search\"\n",
    "    headers = {\n",
    "        \"X-API-KEY\": API_KEY,\n",
    "        \"Content-Type\": \"application/json\"\n",
    "    }\n",
    "\n",
    "    payload = {\n",
    "        \"q\": query,\n",
    "        \"gl\":\"cn\",\n",
    "        \"hl\":\"zh-CN\",\n",
    "        \"num\":1\n",
    "    }\n",
    "\n",
    "    response = httpx.post(url, headers=headers, json=payload)\n",
    "    data = response.json()\n",
    "    # print(data)\n",
    "\n",
    "    if data[\"organic\"]:\n",
    "        return data[\"organic\"][0][\"snippet\"]\n",
    "    else:\n",
    "        return \"没有找到相关信息\""
   ],
   "id": "96c23c2561626467",
   "outputs": [],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:38:45.459510Z",
     "start_time": "2025-10-20T08:38:44.039389Z"
    }
   },
   "cell_type": "code",
   "source": "google_search(\"法国的首都是哪？\")",
   "id": "89a5fab2f2b50d43",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'巴黎（法语：Paris，发音：[paʁi]）是法国的首都及最大都市，同时是法兰西岛大区首府，为法国的政治、经济与文化中心，隶属法兰西岛大区之下的巴黎省（编号第75省），市与省的辖域重 ...'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:38:48.564678Z",
     "start_time": "2025-10-20T08:38:48.561729Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def calculate(what):\n",
    "    return eval(what)"
   ],
   "id": "4ee528278ff9b9ab",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:38:49.480181Z",
     "start_time": "2025-10-20T08:38:49.477243Z"
    }
   },
   "cell_type": "code",
   "source": "print(calculate(\"10/3\"))",
   "id": "e77896c8d2beb81",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.3333333333333335\n"
     ]
    }
   ],
   "execution_count": 6
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:41:13.813865Z",
     "start_time": "2025-10-20T08:41:13.811528Z"
    }
   },
   "cell_type": "code",
   "source": [
    "known_actions = {\n",
    "    \"google_search\": google_search,\n",
    "    \"calculate\": calculate\n",
    "}"
   ],
   "id": "16cee80466383819",
   "outputs": [],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:41:17.459109Z",
     "start_time": "2025-10-20T08:41:17.119853Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from openai import OpenAI\n",
    "import httpx\n",
    "\n",
    "client = OpenAI()\n",
    "\n",
    "# https://til.simonwillison.net/llms/python-react-pattern\n",
    "class ChatBot:\n",
    "    def __init__(self, system=\"\"):\n",
    "        self.system = system\n",
    "        self.messages = []\n",
    "        if self.system:\n",
    "            self.messages.append({\"role\": \"system\", \"content\": system})\n",
    "\n",
    "    def __call__(self, message):\n",
    "        self.messages.append({\"role\": \"user\", \"content\": message})\n",
    "        result = self.execute()\n",
    "        self.messages.append({\"role\": \"assistant\", \"content\": result})\n",
    "        return result\n",
    "\n",
    "    def execute(self):\n",
    "        completion = client.chat.completions.create(model=\"gpt-4.1\", messages=self.messages)\n",
    "\n",
    "        return completion.choices[0].message.content"
   ],
   "id": "3b24b14e24afeca2",
   "outputs": [],
   "execution_count": 8
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:54:05.038544Z",
     "start_time": "2025-10-20T08:54:05.035831Z"
    }
   },
   "cell_type": "code",
   "source": [
    "system_prompt = \"\"\"\n",
    "You run in a loop of Thought, Action, Observation, Answer.\n",
    "At the end of the loop you output an Answer\n",
    "Use Thought to describe your thoughts about the question you have been asked.\n",
    "Use Action to run one of the actions available to you.\n",
    "Observation will be the result of running those actions.\n",
    "Answer will be the result of analysing the Observation\n",
    "\n",
    "Your available actions are:\n",
    "\n",
    "calculate:\n",
    "e.g. calculate: 4 * 7 / 3\n",
    "Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary\n",
    "\n",
    "google_search:\n",
    "e.g. google_search: Django\n",
    "Returns a info from searching SerperAPI\n",
    "\n",
    "Always look things up on google_search if you have the opportunity to do so.\n",
    "\n",
    "Example session:\n",
    "\n",
    "Question: What is the capital of France?\n",
    "Thought: I should look up France on SerperAPI\n",
    "Action: google_search: What is the capital of France?\n",
    "PAUSE\n",
    "\n",
    "You will be called again with this:\n",
    "\n",
    "Observation: France is a country. The capital is Paris.\n",
    "Thought: I think I have found the answer\n",
    "Action: The capital of France is Paris.\n",
    "You should then call the appropriate action and determine the answer from the result\n",
    "\n",
    "You then output:\n",
    "\n",
    "Answer: The capital of France is Paris\n",
    "\"\"\".strip()"
   ],
   "id": "2811c82fcfefb908",
   "outputs": [],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:54:08.252366Z",
     "start_time": "2025-10-20T08:54:08.248556Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import re\n",
    "\n",
    "action_re = re.compile(r'^Action: (\\w+): (.*)$')\n",
    "\n",
    "match = action_re.match(\"Action: google_search: What is the capital of France?\")\n",
    "if match:\n",
    "    print(match.group(1))\n",
    "    print(match.group(2))"
   ],
   "id": "fec098595de5350d",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "google_search\n",
      "What is the capital of France?\n"
     ]
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:54:32.948930Z",
     "start_time": "2025-10-20T08:54:32.943313Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import re\n",
    "\n",
    "action_re = re.compile(r'^Action: (\\w+): (.*)$')\n",
    "\n",
    "def react_agent_query(question, max_turns=5):\n",
    "    i = 0\n",
    "    bot = ChatBot(system_prompt)\n",
    "    next_prompt = question\n",
    "    while i < max_turns:\n",
    "        i += 1\n",
    "        result = bot(next_prompt)\n",
    "        print(result)\n",
    "        actions = [action_re.match(a) for a in result.split('\\n') if action_re.match(a)]\n",
    "        if actions:\n",
    "            # There is an action to run\n",
    "            action, action_input = actions[0].groups()\n",
    "            if action not in known_actions:\n",
    "                raise Exception(\"Unknown action: {}: {}\".format(action, action_input))\n",
    "            print(\" -- running {} {}\".format(action, action_input))\n",
    "            observation = known_actions[action](action_input)\n",
    "            print(\"Observation:\", observation)\n",
    "            next_prompt = \"Observation: {}\".format(observation)\n",
    "        else:\n",
    "            return"
   ],
   "id": "85d9925f211ce3b",
   "outputs": [],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T08:54:40.591161Z",
     "start_time": "2025-10-20T08:54:35.329945Z"
    }
   },
   "cell_type": "code",
   "source": "react_agent_query(\"世界最高峰是哪个？\")",
   "id": "f66d3e39588af51d",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Thought: 我应该查一下世界最高峰是哪一座山。\n",
      "Action: google_search: 世界最高峰是哪座山？\n",
      "PAUSE\n",
      " -- running google_search 世界最高峰是哪座山？\n",
      "Observation: 珠穆朗玛峰、珠峰（藏语：ཇོ་མོ་གླང་མ，威利转写：Jo mo glang ma，藏语拼音：Qomolangma，香港称额菲尔士峰，台湾称圣母峰），尼泊尔称萨加玛塔峰（尼泊尔语：सगरमाथा，罗马 ...\n",
      "Thought: 我已经找到了答案，世界最高峰是珠穆朗玛峰。\n",
      "Action: 世界最高峰是珠穆朗玛峰。\n",
      "Answer: 世界最高峰是珠穆朗玛峰。\n"
     ]
    }
   ],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T09:26:21.522052Z",
     "start_time": "2025-10-20T09:26:16.180214Z"
    }
   },
   "cell_type": "code",
   "source": "react_agent_query(\"20*15是多少\")",
   "id": "4b7ecfa2e90dee8e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Thought: 我需要计算 20 乘以 15 的结果。\n",
      "Action: calculate: 20 * 15\n",
      "PAUSE\n",
      " -- running calculate 20 * 15\n",
      "Observation: 300\n",
      "Answer: 20*15等于300。\n"
     ]
    }
   ],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T09:26:29.554201Z",
     "start_time": "2025-10-20T09:26:21.544865Z"
    }
   },
   "cell_type": "code",
   "source": "react_agent_query(\"长江和黄河的长度差多少\")",
   "id": "3cd1a3aa78a477a2",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Thought: 我需要查找长江和黄河的长度，然后计算它们的差值。\n",
      "Action: google_search: 长江和黄河的长度分别是多少\n",
      "PAUSE\n",
      " -- running google_search 长江和黄河的长度分别是多少\n",
      "Observation: 长江为中国最长河流，全长6300公里，流域面积180万平方公里，发源于青藏高原各拉丹东雪山。 黄河全长5464公里，流域面积75.2万平方公里，以含沙量高著称。 黑龙江作为中俄界河， ...\n",
      "Thought: 我已经得到了长江全长6300公里，黄河全长5464公里。现在需要计算它们的长度差值。\n",
      "Action: calculate: 6300 - 5464\n",
      "PAUSE\n",
      " -- running calculate 6300 - 5464\n",
      "Observation: 836\n",
      "Answer: 长江比黄河长836公里。\n"
     ]
    }
   ],
   "execution_count": 14
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T09:26:35.380711Z",
     "start_time": "2025-10-20T09:26:29.575486Z"
    }
   },
   "cell_type": "code",
   "source": "react_agent_query(\"长江和黄河的长度差多少\")",
   "id": "cfaf946edffae35d",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Thought: 我需要查找长江和黄河的长度，然后计算它们的差值。\n",
      "Action: google_search: 长江和黄河的长度分别是多少\n",
      "PAUSE\n",
      " -- running google_search 长江和黄河的长度分别是多少\n",
      "Observation: 长江为中国最长河流，全长6300公里，流域面积180万平方公里，发源于青藏高原各拉丹东雪山。 黄河全长5464公里，流域面积75.2万平方公里，以含沙量高著称。 黑龙江作为中俄界河， ...\n",
      "Thought: 已经获取到了长江6300公里和黄河5464公里的长度，现在只需要做减法计算它们的长度差。\n",
      "Action: calculate: 6300 - 5464\n",
      "PAUSE\n",
      " -- running calculate 6300 - 5464\n",
      "Observation: 836\n",
      "Answer: 长江和黄河的长度差是836公里。\n"
     ]
    }
   ],
   "execution_count": 15
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "cb9e1a42efc2e9c7"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
